package com.benshou.bcss.common.exception;

import com.benshou.bcss.common.j17common.annotation.NotEmpty;
import com.benshou.bcss.common.rest.ApiResponse;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.nio.file.AccessDeniedException;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;

/**
 * 全局 Controller 级异常处理器
 *
 * @author <a href="mailto:liuzhenbang@benshou.com">Liu Zhenbang</a>
 */
@RestControllerAdvice
@Slf4j
public class GlobalControllerExceptionHandler {

    /**
     * 处理业务异常
     *
     * @param e 业务异常实例
     * @return API 响应结果
     */
    @ExceptionHandler(BizException.class)
    public ApiResponse<?> handleBizException(BizException e) {
        logger.error("业务异常：", e);
        return ApiResponse.fail(e.code, "业务异常：" + e.getMessage());
    }

    /**
     * 处理参数校验异常
     *
     * @param e 参数校验异常实例
     * @return API 响应结果
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResponse<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        logger.error("参数校验异常：", e);
        return ApiResponse.fail("参数校验异常：" + buildErrorMessageByFieldErrors(e.getFieldErrors()));
    }

    /**
     * 处理参数校验异常
     *
     * @param e 参数校验异常实例
     * @return API 响应结果
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public ApiResponse<?> handleConstraintViolationException(ConstraintViolationException e) {
        logger.error("参数校验异常：", e);
        return ApiResponse.fail("参数校验异常：" + buildErrorMessageByViolations(e.getConstraintViolations()));
    }

    /**
     * 处理权限越界异常
     *
     * @param e 权限越界异常实例
     * @return API 响应结果
     */
    @ExceptionHandler(AccessDeniedException.class)
    public ApiResponse<?> handleAccessDeniedException(AccessDeniedException e) {
        logger.error("权限不足：", e);
        return ApiResponse.fail("权限不足：" + e.getMessage());
    }

    /**
     * 基异常，用于兜底
     *
     * @param e 基异常实例
     * @return API 响应结果
     */
    @ExceptionHandler(Exception.class)
    public ApiResponse<?> handleException(Exception e) {
        logger.error("系统异常：", e);
        return ApiResponse.fail("系统异常：" + e.getMessage());
    }

    /**
     * 构建错误消息
     *
     * @param violations 参数校验结果
     * @return 错误消息
     */
    private String buildErrorMessageByViolations(Collection<? extends ConstraintViolation<?>> violations) {
        if (violations == null || violations.isEmpty()) {
            return "";
        }

        return buildErrorMessage(violations.stream()
                .map(e -> {
                    String message;
                    return Map.entry(
                            Objects.toString(e.getPropertyPath()),
                            (message = e.getMessage()) == null ? "" : message);})
                .toList());
    }

    /**
     * 构建错误消息
     *
     * @param errors 参数校验结果
     * @return 错误消息
     */
    private String buildErrorMessageByFieldErrors(Collection<? extends FieldError> errors) {
        if (errors == null || errors.isEmpty()) {
            return "";
        }

        return buildErrorMessage(errors.stream()
                .map(e -> {
                    String message;
                    return Map.entry(
                            Objects.requireNonNull(e.getField()),
                            (message = e.getDefaultMessage()) == null ? "" : message);})
                .toList());
    }

    /**
     * 构建错误消息
     *
     * @param fieldToErrorEntries 字段值 -> 错误消息映射
     * @return 错误消息
     */
    private String buildErrorMessage(@NotEmpty Collection<Map.Entry<String, String>> fieldToErrorEntries) {
        var messageBuilder = new StringBuilder();
        fieldToErrorEntries.forEach(e -> messageBuilder
                .append("\n- ")
                .append(e.getKey())
                .append(": ")
                .append(e.getValue()));
        return messageBuilder.toString();
    }
}
